package ir;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class StubMethodNode implements Generator {

  private Method method;
  
  public StubMethodNode(Method m) {
    method = m;
  }

  public String toString() {
    return method.getName();
  }

  //      make sig: if anything in Return or Params is eligible for
  //                  translation, do it. 
  //      invoke the equivalent existing stub method using any params.
  //      assign to a returnable, and make sure to translate the returnable.
  public String gen() {
    
    Class<?> methodReturnType = method.getReturnType();
    String returnable = getGwtifiedType(methodReturnType);
    
    // TODO gwtify the args incoming
    String paramsList = getParamsList();    

    String body = getMethodBody();
    
    
    return INDENT + "public " + returnable + " " + method.getName() +
      "(" + paramsList + ")\n" + 
        INDENT + "{\n" + body + INDENT +"}\n\n";
  }
  
  /**
   * Generates the method signature for the Service Interface. It is of the form:
   *        public GwtSiteData[] getSites();
   * @return
   */
  public String genServiceInterfaceSignature() {
    return "public " + getGwtifiedType(method.getReturnType()) + 
      " " + method.getName() + "(" + getParamsList() + ");"; 
  }
  
  /**
   * Generates the method signature for the Async Service Interface.
   * It is of the form:
   *        public void getSites(AsyncCallback callback);
   * The chief differences are that the async method
   * has a void return type and an additional parameter: the callback.
   * 
   * @return
   */
  public String genServiceAsyncInterfaceSignature() {
    String regular_params = getParamsList();
    String total_params = regular_params;
    if (regular_params.length() > 0)
      total_params += ", ";
      
    total_params += "AsyncCallback callback";
    return "public void " + 
      method.getName() + "(" + total_params + ");"; 
  }

  
  // generate glue code that invokes the Soap Stub and maps results
  /*
   * public GwtOrderData[] getOrdersByBilingNUmber(String billingNumber) {
   *        OrderData[] orderData = stub.getOrdersByBillingNumber();
   *        return Soap2Gwt(orderData);
   *    }
   */
  private String getMethodBody() {
    
    // Apply Gwt2Soap on Args
    // Invoke stub method by same name
    // if ReturnType != void,
    //  return Soap2Gwt(returned)
    // ARGS: apply Gwt2Soap 
    // RETURNS: apply Soap2Gwt
    
    String body = "";
    String args_convert = "";
    
    // Translate Arguments
    Class<?>[] parameterTypes = method.getParameterTypes();
    String[] argsList = new String[parameterTypes.length];
    for (int i = 0; i < parameterTypes.length; i++) {
      if (isTypeQualified(parameterTypes[i]))
          argsList[i] = "Translator.instance().Gwt2Soap(arg" + i + ")";
      else 
        argsList[i] = "arg" + i;
    }
    String args = implode(argsList, ", ");
    
    // Return Statement
    Class<?> returnType = method.getReturnType();
    String returnTypeName = returnType.getSimpleName();
    if (! returnTypeName.equals("void")) {
      
//      if (method.getReturnType().isp)
      String returntype_nil = null;
      if (returnType.isPrimitive()) {
        if (returnType.equals(char.class))
          returntype_nil = "''";
        else if (returnType.equals(String.class))
          returntype_nil = "\"\"";
        else returntype_nil = "0";
      }
      else returntype_nil = "null";
      
      String declare_return = returnTypeName + " returnable = " + returntype_nil + ";\n";
      
      String glue = INDENT + INDENT + INDENT + "returnable = stub." + method.getName() + 
          "(" + args + ");";
      glue = encloseTryCatch(glue);
      
      body += INDENT + INDENT + declare_return + glue + "\n";
      
      String return_statement = "";
      if (isTypeQualified(returnType))
        return_statement = INDENT + INDENT + "return Translator.instance().Soap2Gwt(returnable);\n";
      else 
        return_statement = INDENT + INDENT + "return returnable;\n";
      body += return_statement;
    }
    
    return body;
  }
  
  private String encloseTryCatch(String content) {
    Class<?>[] exceptionTypes = method.getExceptionTypes();
    
    String enclosure = "";
    
    /**
     * Must SORT BY the Type Hierarchy!!! top of hierarchy comes last.
     * 
     * Use a closure with a sort routine to pass these in a List and sort.
     * 
     */
    
    List<ExceptionClass> sortedExceptions = new ArrayList<ExceptionClass>();
    for (Class<?> t : exceptionTypes) {
      sortedExceptions.add(new ExceptionClass(t));
    }
    Collections.sort(sortedExceptions);
    
    
    if (exceptionTypes.length > 0) {
      enclosure = INDENT + INDENT + "try {\n " + content + "} \n";
      for (ExceptionClass exception : sortedExceptions) {
        enclosure += INDENT + INDENT + "catch (" + exception.getType().getSimpleName() + " e) { e.printStackTrace(); }\n";
      }
          
    }
    
    return enclosure;
  }
  
  /**
   * Generates the Parameter declarator of the method signature;
   * this is of the form
   *    PrimitiveType arg0, GwtType1 arg1, GwtType2 arg2, ...
   * @return
   */
  private String getParamsList() {
 // TODO gwtify the args incoming
    String args = "";
    Class<?>[] parameterTypes = method.getParameterTypes();
    String[] params = new String[parameterTypes.length];
    for (int i = 0; i < parameterTypes.length; i++)
      params[i] = getGwtifiedType(parameterTypes[i]) + " arg" + i;
    args = implode(params, ", ");
    return args;
  }
  
  private String implode(String[] ary, String delim) {
    String out = "";
    for(int i=0; i<ary.length; i++) {
        if(i!=0) { out += delim; }
        out += ary[i];
    }
    return out;
  }
  
  private String getGwtifiedType(Class<?> typeObject) {
    String typeName = typeObject.getSimpleName();
    
    // TODO handle arrays of primitives
    // If Returnable is a complex data type, get the GwtName
    if (typeObject.isArray()) {
      Class<?> componentType = typeObject.getComponentType();
        if (! componentType.isPrimitive() && 
              ! componentType.equals(String.class) ) {
          typeName = "Gwt" + componentType.getSimpleName() + "[]";
        }
    }
    else if (! typeObject.isPrimitive() && ! typeObject.equals(String.class) ) {
        typeName = "Gwt" + typeObject.getSimpleName();
    }
    return typeName;
  }
  
  private boolean isTypeQualified(Class<?> type) {
    
    // check the component type if we're dealing with an array
    Class<?> c = type;
    if (c.isArray())
      type = c.getComponentType();
    else type = c;
    
    return (! type.isPrimitive() && ! type.equals(String.class) 
        && ! type.getCanonicalName().contains("java.lang") && ! type.getCanonicalName().contains("java.util"));
  }
  
  public Set<Class<?>> getRequiredExceptions() {
    Set<Class<?>> exceptions = new HashSet<Class<?>>();
    exceptions.addAll(Arrays.asList(method.getExceptionTypes()));
    return exceptions;
  }


  private class ExceptionClass implements Comparable {

    private Class<?> t;
    
    public ExceptionClass(Class t) {
      this.t = t;
    }
    
    @Override
    public int compareTo(Object o) {
      ExceptionClass c = (ExceptionClass) o;
      Set<Class<?>> superClasses = getSuperClasses(t);
      if (superClasses.contains(c.getType()))
        return -1;
      else return 1;
    }
    
    public Set<Class<?>> getSuperClasses(Class<?> c) {
      if ( c.equals(Object.class))
        return new HashSet<Class<?>>();
      else {
        HashSet<Class<?>> supers = new HashSet<Class<?>>();
        supers.add(c.getSuperclass());
        supers.addAll(getSuperClasses(c.getSuperclass()));
        return supers;
      }
    }
    
    public Class<?> getType() {
      return t;
    }
  }

  
}
